##############################################################
## @file    ipclient.py
## @date    2011-06-20
## @author  ganxiangle@gmail.com
## @brief   python implementation of Ipclient
##
##############################################################

import socket
import struct
import hashlib
import sys
import time

PROTO_BANNER = '\x82\x23'
SERVER = '172.16.12.11'
CONTROL_PORT = 5300
REFRESH_PORT = 5301
CMDPACKET_LENGTH = 300
REFRESHPACKET_LENGTH = 500
TIMEOUT = 0.1
REFRESH_INTERVAL = 30
CMDS = { 
    'LOGIN': '\x1f', 
    'CHALLEGE_PASSWD': '\x20', 
    'PASSWORD': '\x21',
    'LOGIN_RESULT': '\x22',
    'REFRESH': '\x1e',
    'REFRESH_REPLY': '\x1f'
}
class CmdPacket:    
    def __init__(self, userid, password):        
        self.userid = userid
        self.password = password        
        self.refresh_key = 0
    def get_login_packet(self):
        self.setup_login()
        return self.fill_cmdpacket()
    # called by passing the login reply(contains a a key value)
    def get_passwd_packet(self, login_reply_packet):
        # get the key in the login reply, which used to refresh
        # packet, and construct the password packet.
        the_key = self.getkey_in_packet(login_reply_packet)
        self.refresh_key = the_key -1234 + 1500
        # setup password packet
        self.cmd = CMDS['PASSWORD']
        self.string1 = '+++++'
        self.string2 = self.get_encry_password(the_key - 1234)        
        self.string3 = 'ff:ff:ff:ff:ff:ff'
        self.key = 0x34333231        
        self.string4 = '/////'
        
        return self.fill_cmdpacket()
    # return the refresh key used by RefreshPacket class
    # must after self.get_passwd_packet() to get a meani-
    # ngful refresh_key
    def get_refresh_key(self):        
        return self.refresh_key
    # check if it's challege password packet
    def check_login_reply(self, packet):
        banner, cmd = struct.unpack('<2s c', packet[:3])
        if banner != PROTO_BANNER :
            return -1
        elif cmd != CMDS['CHALLEGE_PASSWD'] :
            return -1
        else:
            return 0
    def check_passwd_reply(self, packet):
        banner, cmd, ID = struct.unpack('<2s c I', packet[:7])
        if banner != PROTO_BANNER:
            return -1
        elif cmd != CMDS['LOGIN_RESULT']:
            return -1
        else:
            return ID
    def get_login_result_string(self, ID):
        RESULTS = { 
            0x1:  'your ip needn\'t use ipclient',
            0xa:  'your account is expired',
            0xb:  'your account is disabled',
            0x14: 'your account has not enough money',
            0x15: 'your accont has not available hours in this month',
            0x16: 'your account has not available flow in this mouth',
            0x19: 'your account cannot be used in this IP',
            0x1e: 'your account cannot be used in this time',
            0x1f: 'please dial later',
            0x20: 'too many users are using this account now',
            0x21: 'ipclient cannot be used for your account',
            0x22: 'please dial later',
            0x63: 'userid or password error'
            }
        return RESULTS[ID]

    def get_encry_password(self, key):
        string = repr(key) + self.password
        string = self.mdstring(string)
        string = (string[:8]).upper() + self.userid
        string = self.mdstring(string)
        string = (string.upper())[:30]
        return string
    def getkey_in_packet(self, packet):        
        banner, cmd, rep, un1, len1 = struct.unpack('<2sc III', packet[:15])        
        length = struct.calcsize('<2sc III') + len1        
        len2, = struct.unpack('I', packet[length:(length+4)])
        length += len2 + struct.calcsize('<I')
        len3, = struct.unpack('I', packet[length:(length+4)])
        length += len3 + struct.calcsize('<I')
        the_key, = struct.unpack('I', packet[length:(length+4)])        
        return the_key        
    def changestr_add(self, string, delta):
        newstring = ''
        for i in range(len(string)):
            newstring += chr( ord(string[i]) + delta )
        return newstring    
    def setup_login(self):
        self.banner = PROTO_BANNER
        self.cmd  = CMDS['LOGIN']
        self.rep  = 0
        self.un1  = 0
        self.string1 = self.changestr_add(self.userid, -10)
        self.string2 = "-----"
        self.string3 = "*****"
        self.key = 0xffffffff
        self.un2 = 0x0
        self.un3 = 0x0
        self.string4 = "/////"
    def fill_cmdpacket(self):
        packet = struct.pack('<2sc III', self.banner,
                             self.cmd, self.rep,
                             self.un1, len(self.string1) )        
        packet += self.string1
        packet += struct.pack('<I', len(self.string2) )
        packet += self.string2
        packet += struct.pack('<I', len(self.string3) )
        packet += self.string3
        packet += struct.pack('<IIII', self.key, self.un2, self.un3,
                              len(self.string4) )
        packet += self.string4
        length = CMDPACKET_LENGTH - len(packet)
        packet += length * '\x00'
        return packet 
    def mdstring(self, string):
        md5ob = hashlib.md5()
        md5ob.update(string)
        enstr = md5ob.digest()
        s = ''
        for c in enstr:
            s += '{0:0>2x}'.format(ord(c))
        return s
class RefreshPacket:
    def __init__(self, userid, refresh_key):
        self.userid = userid
        self.refresh_key = refresh_key
    def set_refreshkey(self, refresh_key):
        self.refresh_key = refresh_key
    # must be called before setup refresh_key
    def get_refresh_packet(self):
        self.setup_refresh()
        return self.fill_refreshpacket()
    # must be called before setup refresh_key
    def setup_refresh(self):
        self.banner = PROTO_BANNER
        self.cmd = CMDS['REFRESH']        
        self.id  = self.refresh_key
        self.un1 = self.un2 = self.un3 = self.un4 \
            = self.un5 = 0                
        self.string1 = self.userid
        self.string2 = 'refreshing'
        self.string3 = '=========='        
    def fill_refreshpacket(self):
        packet = struct.pack('<2sc III III', self.banner,
                             self.cmd, self.id, self.un1,
                             self.un2, self.un3, self.un4,
                             self.un5 )
        packet += struct.pack('<i', len(self.string1) )
        packet += self.string1
        packet += struct.pack('<i', len(self.string2) )
        packet += self.string2
        packet += struct.pack('<i', len(self.string3) )
        packet += self.string3
        length = 500 - len(packet)
        packet += length * '\x00'
        return packet    
    
class Ipclient:
    def __init__(self, userid, password):
        self.userid = userid
        self.password = password
        self.cmdpacket = CmdPacket(userid, password)
        # not set the refreshkey
        self.refreshpacket = RefreshPacket(userid, 0)
        self.cmd_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.cmd_sock.settimeout(TIMEOUT)
        self.refresh_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)        
        self.refresh_sock.settimeout(TIMEOUT)        
    def login(self):
        ret = 0
        try:
            packet = self.cmdpacket.get_login_packet()
            self.cmd_sock.sendto(packet, 0, (SERVER, CONTROL_PORT))
            recvpacket, recvaddr = self.cmd_sock.recvfrom(CMDPACKET_LENGTH)

            res = self.cmdpacket.check_login_reply(recvpacket)
            if res == -1:
                self.cmd_sock.close()
                ret = -1
            
            packet = self.cmdpacket.get_passwd_packet(recvpacket)
            self.cmd_sock.sendto(packet, 0, (SERVER, CONTROL_PORT))
            recvpacket, recvaddr = self.cmd_sock.recvfrom(CMDPACKET_LENGTH)
            res = self.cmdpacket.check_passwd_reply(recvpacket)
            if res < 0:
                self.cmd_sock.close()
                ret = -1
            elif res == 0:
                print '===Login OK===:', self.userid                
            elif res > 0:
                print self.cmdpacket.get_login_result_string(res)
                ret = -1
        except socket.error as ex:
            print ex
            ret = -1
        except socket.timeout:
            print "timeout!"
            ret = -1
        finally:
            self.cmd_sock.close()
        return ret
    def refresh(self):
        self.refreshpacket.set_refreshkey( self.cmdpacket.get_refresh_key() )
        packet = self.refreshpacket.get_refresh_packet()

        terminate = 0
        while True:
            self.refresh_sock.sendto(packet, 0, (SERVER, REFRESH_PORT))
            for i in range(10):
                try:                    
                    recvpacket, recvaddr = self.refresh_sock.recvfrom(REFRESHPACKET_LENGTH)                    
                except socket.timeout as ex:
                    print 'timout in recving refresh packet reply'
                else:
                    break
                if i == 9:
                    terminate = 1                    
            if terminate != 0:
                break
            else:
                time.sleep(REFRESH_INTERVAL)

if __name__ == '__main__':    
    userid = ''
    password = ''
    if sys.argv[1] != '':
        userid = sys.argv[1]
    if sys.argv[2] != '':
        password = sys.argv[2]
    
    ipclient = Ipclient(userid, password)
    res = ipclient.login()
    if res == 0: 
        ipclient.refresh()
        

